/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package com.inspur.edp.lcm.metadata.core;

import com.inspur.edp.lcm.metadata.api.entity.GspMetadata;
import com.inspur.edp.lcm.metadata.api.entity.LocalRepoPkg;
import com.inspur.edp.lcm.metadata.api.entity.MdPkg;
import com.inspur.edp.lcm.metadata.api.entity.MetadataPackage;
import com.inspur.edp.lcm.metadata.api.entity.ProcessMode;
import com.inspur.edp.lcm.metadata.api.entity.metadataindex.LocalMetadataIndexItem;
import com.inspur.edp.lcm.metadata.common.FileServiceImp;
import com.inspur.edp.lcm.metadata.common.Utils;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import lombok.extern.slf4j.Slf4j;


/**
 * 元数据索引服务
 *
 * @author zhaoleitr
 */
@Slf4j
public class MetadataIndexCacheService {
    private static MetadataIndexCacheService singleton = null;
    private static Lock lock = new ReentrantLock();
    private final FileServiceImp fileServiceImp = new FileServiceImp();
    /**
     * 本地元数据包中元数据<元数据标识，元数据信息缓存项>
     */
    private Map<String, LocalMetadataIndexItem> localPackagesMdIndexDic;
    /**
     * <元数据包编码.mdpkg, 元数据标识列表>，规则元数据包code
     */
    private Map<String, MdPkg> localPackagesMdPkgIndexDic;
    /**
     * 本地NugetPkg缓存中元数据索引 <元数据标识_nuget version，元数据信息缓存项>
     */
    private Map<String, LocalMetadataIndexItem> localRepoCacheMdIndexDic;
    /**
     * <mdPkg name_nugetversion, 元数据标识列表>
     */
    private Map<String, MdPkg> localRepoCacheMdPkgIndexDic;
    private Map<String, LocalRepoPkg> localRepoCachePkgIndexDic;

    private MetadataIndexCacheService() {
        resetLocalPackagesMdIndex();
        resetLocalRepoCacheMdIndex();
    }

    public static MetadataIndexCacheService getInstance() {
        if (singleton == null) {
            singleton = new MetadataIndexCacheService();
        }
        return singleton;
    }

    /**
     * <nugetPkg name + version, 元数据标识列表>
     */
    public List<LocalMetadataIndexItem> getLocalPackagesMdIndexDic(String packagePath) {
        refreshLocalPackageMdIndex(packagePath);
        return new ArrayList<>(localPackagesMdIndexDic.values());
    }

    public Map<String, LocalRepoPkg> getLocalRepoCachePkgIndexDic() {
//        refreshLocalRepoCache();
        return new ConcurrentHashMap<>(localRepoCachePkgIndexDic);
    }

    private void refreshLocalRepoCache(String mavenPath) {
        lock.lock();
        try {
            // 1. 对比本地元数据包是否为最新，是否有增删
            String mavenPackageLocation = mavenPath;
            if (fileServiceImp.isDirectoryExist(mavenPackageLocation)) {
                resetLocalRepoCacheMdIndex();
                return;
            }
            Map<String, File> mavenPkgDirs = new HashMap();
            fileServiceImp.getDirectorys(mavenPackageLocation).forEach(dir -> {
                mavenPkgDirs.put(dir.getName(), dir);
            });
            // nuget包缓存索引信息
            List<String> localMavenPkgCacheIndexList = new ArrayList<>();
            for (Map.Entry<String, LocalRepoPkg> key : localRepoCachePkgIndexDic.entrySet()) {
                localMavenPkgCacheIndexList.add(key.getKey());
            }
            // 2. 如果1中有更新，更新索引
            // 删掉的
            if (mavenPkgDirs.size() < 1) {
                if (localMavenPkgCacheIndexList.size() > 0) {
                    resetLocalRepoCacheMdIndex();
                }
            } else if (localMavenPkgCacheIndexList.size() > 0) {
                for (String mavenPkgIndex : localMavenPkgCacheIndexList) {
                    if (!mavenPkgDirs.containsKey(mavenPkgIndex)) {
                        deleteLocalRepoCachePkgIndex(mavenPkgIndex);
                    }
                }
            }
            // 新增的
//        if (mavenPkgDirs.size() > 0) {
//            for(Map.Entry<String, File> entry : mavenPkgDirs.entrySet()){
//                String mavenPkgDir = entry.getKey();
//                String mavenPkgPath = entry.getValue();
//            }
//        }
        } finally {
            lock.unlock();
        }
    }

    private void deleteLocalRepoCachePkgIndex(String mavenPkgName) {
        lock.lock();
        try {
            toDeleteLocalRepoCachePkgIndex(mavenPkgName);
        } finally {
            lock.unlock();
        }

    }

    private void toDeleteLocalRepoCachePkgIndex(String mavenPkgName) {
        LocalRepoPkg localRepoPkg = localRepoCachePkgIndexDic.get(mavenPkgName);
        if (localRepoPkg != null && localRepoPkg.getMdPkgs() != null && localRepoPkg.getMdPkgs().size() > 0) {
            localPackagesMdPkgIndexDic.remove(mavenPkgName);
            for (MdPkg mdPkg : localRepoPkg.getMdPkgs()) {
                MdPkg mdpkg = localRepoCacheMdPkgIndexDic.get(makeKey(mdPkg.getMdPkgName(), localRepoPkg.getVersion()));
                if (mdpkg != null && mdpkg.getMetadataIds() != null && mdpkg.getMetadataIds().size() > 0) {
                    localRepoCacheMdPkgIndexDic.remove(makeKey(mdPkg.getMdPkgName(), localRepoPkg.getVersion()));
                    mdpkg.getMetadataIds().forEach(id -> {
                        if (localPackagesMdIndexDic.get(id) != null) {
                            localRepoCacheMdIndexDic.remove(makeKey(id, localRepoPkg.getVersion()));
                        }
                    });
                }
            }
//            mdPkg.getMetadataIds().forEach(id->{
//                if (localPackagesMdIndexDic.get(id) != null) {
//                    localPackagesMdIndexDic.remove(id);
//                }
//            });
        }
    }

    private String makeKey(String a, String b) {
        return a + "_" + b;
    }

    private void resetLocalRepoCacheMdIndex() {
        localRepoCacheMdIndexDic = new ConcurrentHashMap<>();
        localRepoCacheMdPkgIndexDic = new ConcurrentHashMap<>();
        localRepoCachePkgIndexDic = new ConcurrentHashMap<>();
    }

    /***
     * 更新pakcages目录索引
     * @author zhongchq
     **/
    private void refreshLocalPackageMdIndex(String packagePath) {
        lock.lock();
        // 1. 对比本地元数据包是否为最新，是否有增删
        try {
            String metadataPackageLocation = packagePath;
            if (!fileServiceImp.isDirectoryExist(metadataPackageLocation)) {
                resetLocalPackagesMdIndex();
                return;
            }
            List<File> mdPkgDirInfoNames = fileServiceImp.getDirectorys(metadataPackageLocation);
            //缓存目录下元数据文件名
            List<String> mdPkgFileInfoNames = new ArrayList<>();
            mdPkgDirInfoNames.forEach(dirname -> {
                fileServiceImp.getAllFiles(dirname.toString()).forEach(file -> {
                    mdPkgFileInfoNames.add(file.getName());
                });
            });
            // 元数据包文件索引信息
            List<String> localMdPkgCodeList = new ArrayList<>();
            for (Map.Entry<String, MdPkg> key : localPackagesMdPkgIndexDic.entrySet()) {
                localMdPkgCodeList.add(key.getKey());
            }
            // 2. 如果1中有更新，更新索引
            // 删掉的
            if (mdPkgFileInfoNames.size() < 1) {
                if (localMdPkgCodeList.size() > 0) {
                    resetLocalPackagesMdIndex();
                }
            } else if (localMdPkgCodeList.size() > 0) {
                localMdPkgCodeList.forEach(mdPkgCode -> {
                    if (!mdPkgFileInfoNames.contains(mdPkgCode)) {
                        deleteLocalPackagesMdPkgIndex(mdPkgCode);
                    }
                });
            }
            // 新增的
            if (mdPkgFileInfoNames.size() > 0) {
                mdPkgFileInfoNames.forEach(mdPkgFileInfoName -> {
                    if (!localMdPkgCodeList.contains(mdPkgFileInfoName)) {
                        MetadataPackage mdPkg;
                        mdPkg = new MetadataCoreManager().getMetadataPackageInfo(mdPkgFileInfoName, packagePath);
                        try {
                            if (!(mdPkg.getMetadataList() == null ||
                                    mdPkg.getMetadataList().size() < 1)) {
                                List<LocalMetadataIndexItem> items = new ArrayList<>();
                                for (GspMetadata gspMetadata : mdPkg.getMetadataList()) {
                                    items.add(assemblyLocalMdIndexItem(gspMetadata, mdPkg, null));
                                }
                                MdPkg mdPkgCache = assemblyLocalMdPkg(mdPkg, items);
                                addLocalPackagesMdIndex(mdPkgCache, items);
                            }
                        } catch (Exception e) {
                            log.error("Parse Error: " + mdPkgFileInfoName);
                            e.printStackTrace();
                        }
                    }
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    private void addLocalPackagesMdIndex(MdPkg mdPkg, List<LocalMetadataIndexItem> items) {
        lock.lock();
        try {
            localPackagesMdPkgIndexDic.put(mdPkg.getMdPkgName() + Utils.getMetadataPackageExtension(), mdPkg);
            if (items != null && items.size() > 0) {
                items.forEach(item -> {
                    localPackagesMdIndexDic.put(item.getId(), item);
                });
            }
        } finally {
            lock.unlock();
        }
    }

    private MdPkg assemblyLocalMdPkg(MetadataPackage mdPkg, List<LocalMetadataIndexItem> items) {

        List<MdPkg> depMdPkgs = new ArrayList<>();
        if (mdPkg.getReference() != null && mdPkg.getReference().size() > 0) {
            mdPkg.getReference().forEach(mdRef -> {
                MdPkg md = new MdPkg();
                md.setMdPkgName(mdRef.getDepententPackage().getName());
                depMdPkgs.add(md);
            });
        }
        MdPkg result = new MdPkg();
        result.setMdPkgName(mdPkg.getHeader().getName());
        result.setDepMdPkgs(depMdPkgs);
        HashSet<String> hashSet = new HashSet<>();
        items.forEach(item -> {
            hashSet.add(item.getId());
        });
        result.setMetadataIds(items.size() < 1 ? new HashSet<>() : hashSet);
        return result;
    }

    private LocalMetadataIndexItem assemblyLocalMdIndexItem(GspMetadata md, MetadataPackage mdPkg, LocalRepoPkg repoPkg) {
        if (md == null) {
            return null;
        }
        if (md.getHeader() == null) {
            return null;
        }
        LocalMetadataIndexItem item = new LocalMetadataIndexItem();
        item.setId(md.getHeader().getId());
        item.setCode(md.getHeader().getCode());
        item.setName(md.getHeader().getName());
        item.setNameSpace(md.getHeader().getNameSpace());
        item.setFileName(md.getHeader().getFileName());
        item.setType(md.getHeader().getType());
        item.setMdPkgName(mdPkg.getHeader().getName());
        item.setProcessMode(mdPkg.getHeader().getProcessMode() == null ? ProcessMode.generation.toString() : mdPkg.getHeader().getProcessMode().toString());
        item.setRelativePath(md.getRelativePath());
        if (repoPkg != null) {
            item.setRepoPkg(repoPkg);
        }
        return item;
    }

    private void deleteLocalPackagesMdPkgIndex(String mdPkgName) {
        lock.lock();
        try {
            MdPkg mdPkg = localPackagesMdPkgIndexDic.get(mdPkgName);
            if (mdPkg != null) {
                localPackagesMdPkgIndexDic.remove(mdPkgName);
                mdPkg.getMetadataIds().forEach(id -> {
                    if (localPackagesMdIndexDic.get(id) != null) {
                        localPackagesMdIndexDic.remove(id);
                    }
                });
            }
        } finally {
            lock.unlock();
        }
    }

    private void resetLocalPackagesMdIndex() {
        lock.lock();
        try {
            localPackagesMdIndexDic = new ConcurrentHashMap<>();
            localPackagesMdPkgIndexDic = new ConcurrentHashMap<>();
        } finally {
            lock.unlock();
        }
    }

    public boolean isExistInLocalPackages(String pkgName) {

        if (pkgName == null || pkgName.isEmpty()) {
            return false;
        }
        if (!pkgName.endsWith(Utils.getMetadataPackageExtension())) {
            pkgName = pkgName + Utils.getMetadataPackageExtension();
        }
        return localPackagesMdPkgIndexDic.containsKey(pkgName);
    }
}
